#ifndef ALGORITHM_ANNEALING__
#define ALGORITHM_ANNEALING__

#include <cmath>

#include "algorithm.h"
#include "area.h"
#include "options_annealing.h"
#include "algorithm_sequential.h"
#include "options_sequential.h"
#include "test_manager.h"

/** Abstraktni zakladni trida pro chladici mechanizmus simulovaneho zihani (AnnealingAlgorithm).
 * Aktualizuje teplotu latky v zavislosti na vyvoji algoritmu. Potomci reprezentuji ruzne 
 * chladici strategie.
 */
class Cooler {
public:
	/** Defaultni konstruktor nastavi chladici faktor a pocet vysilacu na mape.
	 * \param alpha chladici faktor
	 * \param size celkovy pocet vysilacu na mape
	 */
	Cooler(double alpha, int size): loop_count(size), temperature(1) {
		this->alpha = alpha;
		this->size = size;
	}
	/** Zacatek chlazeni, inicializace teploty.
	 */
	virtual void Start() {
		loop_count = size;
		temperature = 1;
	}
	/** Vrati aktualni teplotu.
	 * \return aktualni teplota
	 */
	double Temperature() const {
		return temperature;
	}
	/** Zdvojnasobi aktualni teplotu, pokud je mensi nez 100.
	 * \return true, pokud se teplotu povedlo zdvojnasobit
	 */
	bool DoubleTemperature() {
		if (temperature > 100) return false;
		else {
			temperature *= 2;
			return true;
		}
	}
	/** Metoda urcena vyuzivana chlazenimi zavislymi na kvalite vytvarenych reseni.
	 * Pri vytvoreni kazdeho reseni je chladici mechanizmus informovan o jeho kvalite touto metodou.
	 * \param energy energie vytvoreneho reseni
	 */
	virtual void Energy(int energy) {}
	/** Vrati pocet smycek algoritmu.
	 * \return pocet smycek
	 */
	int LoopCount() const {
		return loop_count;
	};
	/** Virtualni metoda volana pri potrebe upravit teplotu.
	 */
	virtual void Update() = 0;
protected:
	double alpha; ///< chladici faktor
	double temperature; ///< aktualni teplota latky
	int loop_count; ///< aktualni pocet smycek
	int size; ///< pocet vysilacu na mape
};

/** Geometricka chladici strategie. V kazdem kroku provede snizeni teploty na jeho alpha-nasobek.
 */
class GeometricCooler: public Cooler {
public:
	/** Defaultni konstruktor inicializuje zakladni tridu.
	 * \param alpha chladici faktor
	 * \param size pocet vysilacu na mape
	 */
	GeometricCooler(double alpha, int size): Cooler(alpha, size) {}
	/** Uprava aktualni teploty. Teplota je snizena na jeji alpha-nasobek.
	 */
	void Update() {
		temperature *= alpha;
	}
};

/** Chladici strategie zvysujici pocet smycek. V kazdem kroku je krome snizeni teploty jeste
 * zvysen pocet smycek algoritmu (vydelenim parametrem alpha).
 */
class CostaCooler: public Cooler {
public:
	/** Defaultni konstruktor inicializuje zakladni tridu.
	 * \param alpha chladici faktor
	 * \param size pocet vysilacu na mape
	 */
	CostaCooler(double alpha, int size): Cooler(alpha, size) {}
	/** Uprava aktualni teploty a poctu smycek. Teplota je vynasobena parametrem alpha, pocet
	 * smycek naopak vydelen (pokud jiz neni moc veliky).
	 */
	void Update() {
		temperature *= alpha;
		if (loop_count < 200 * size) {
			loop_count = (int)(loop_count / alpha);
		}
	}
};

/** Chladici strategie zavisejici na kvalite vytvarenych reseni. Behem kazdeho kroku algoritmu
 * jsou ukladany kvality (energie) jednotlivych reseni, z nich je pote vyvozena nova teplota.
 */
class HurleyCooler: public Cooler {
public:
	/** Defaultni konstruktor inicializuje zakladni tridu a alokuje potrebne misto pro ukladani vysledku.
	 * \param alpha chladici faktor
	 * \param size pocet vysilacu na mape
	 */
	HurleyCooler(double alpha, int size): Cooler(alpha, size), pos(0) {
		energies = new int[size];
	}
	/** Destruktor uvolni pouzite misto.
	 */
	~HurleyCooler() {
		delete[] energies;
	}
	/** Navic oproti startu obecneho chladice jeste nastavi pozici ve vysledkach na zacatek.
	 */
	virtual void Start() {
		Cooler::Start();
		pos = 0;
	}
	void Energy(int energy);
	void Update();
private:
	int * energies; ///< pole pro ulozeni energii dosazenych resenimi v aktualnim kroku
	int pos; ///< index aktualniho reseni v poli energies
};


/** Reseni simulovaneho zihani. Pamatuje si vysilace porusujici nejakou podminku a tim umoznuje
 * rychlou generaci sousednich reseni. Umoznuje prechod do noveho reseni.
 */
class AnnealingSolution {
public:
	/** Defaultni konstruktor, vytvori reseni nulove energie a alokuje potrebne datove struktury.
	 * \param size pocet vysilacu na mape
	 */
	AnnealingSolution(int size): energy(0), count(0) {
		violating = new int[size];
		violation_mask = new bool[size];
	}
	/** Destruktor, odalokuje pouzite datove struktury.
	 */
	~AnnealingSolution() {
		delete[] violating;
		delete[] violation_mask;
	}
	void MoveTo(int * calls, AnnealingSolution & better);
	int energy, ///< energie aktualniho reseni
		count, ///< pocet vysilacu porusujicich podminky
		* violating; ///< seznam porusujicich vysilacu
	int chosen, ///< index vybraneho vysilace (na nem je provadena zmena)
		frequency; ///< nova frekvence vybraneho vysilace
	bool * violation_mask; ///< binarni maska urcujici vysilace porusujici podminky
};

/** Maska poruseni pamatujici si, ktere vysilace spolu porusuji nejakou podminku.
 */
class ConstraintMask {
public:
	/** Defaultni konstruktor vytvarejici masku dane velikosti.
	 * \param size pocet vysilacu na mape
	 */
	ConstraintMask(int size) {
		this->size = size;
		act_size = size * size;
		mask = new int[act_size];
		Clear();
	}
	/** Vycisti masku tak, ze v ni nebudou zadna poruseni.
	 */
	void Clear() {
		memset(mask, 0, act_size * sizeof(int));
	}
	/** Prida do masky nove poruseni mezi danymi vysilaci.
	 * \param i prvni vysilac
	 * \param j druhy vysilac
	 */
	void Add(int i, int j) {
		Min(i, j);
		mask[size * i + j] = 1;
	}
	/** Vrati, zda jiz je v masce pro dane vysilace ulozeno poruseni.
	 * \param i prvni vysilac
	 * \param j druhy vysilac
	 * \param set true, pokud ma byt toto poruseni pridano do masky
	 * \return true, pokud jiz mezi vysilaci existuje nejake poruseni
	 */
	bool IsSet(int i, int j, bool set = false) {
		Min(i, j);
		if (mask[size * i + j] != 0) {
			return true;
		}
		else if (set) {
			Add(i, j);
		}
		return false;
	}
private:
	/** Setridi parametry podle velikosti.
	 * \param i vysledna mensi hodnota
	 * \param j vysledna vetsi hodnota
	 */
	void Min(int & i, int & j) {
		if (i > j) {
			std::swap(i, j);
		}
	}
	int size; ///< pocet vysilacu
	int act_size; ///< velikost masky (size^2)
	int * mask; ///< maska poruseni
};

/** Algoritmus simulovaneho zihani. Optimalizacni algoritmus zalozeny na vylepsovani aktualniho reseni.
 * Iterativne prohledava sousedy reseni, pokud najde lepsiho souseda, tak jim aktualni reseni nahradi.
 * S urcitou pravdepodobnosti umernou teplote (Cooler) prijima i horsi sousedni reseni.
 */
class AnnealingAlgorithm: public Algorithm {
public:
	/** Defaultni konstruktor simulovaneho zihani.
	 * \param o nastaveni simulovaneho zihani
	 */
	AnnealingAlgorithm(Options * o): Algorithm(o), options((AnnealingOptions *)o) {}
	/** Konstruktor rovnou prirazujici i mapu se zadanim.
	 * \param a mapa zadani
	 * \param o nastaveni simulovaneho zihani
	 */
	AnnealingAlgorithm(Area * a, Options * o): Algorithm(o), options((AnnealingOptions *)o) {
		area = a;
	}
	/** Vraci, zda je algoritmus nastaven tak, aby vypocty se vsemi chladicimi strategiemi.
	 * \return true, pokud ano 
	 */
	bool AllConfigurations() {
		options->Log() << "Vse: " << options->all;
		return options->all;
	}
	/** Nacte nasledujici chladici strategii.
	 * \return true, pokud dalsi strategie existuje
	 */
	bool Next() {
		/* clear the area and load next configuration */
		area->clear();
		return options->Next();
	}
	bool Solve();
	/** Do logu vypise informace o algoritmu.
	 */
	void PrintInfo() {
		options->Log() << "Simulated annealing (simulovane zihani ~ tuhnuti) - sestava z vygenerovani "
			<< "pocatecniho reseni pomoci sekvenciho algoritmu. Pote se pokousi vzdy odstranit nejvetsi "
			<< "frekvenci (vsechny anteny, ktere ji pouzivaly, jsou prenastaveny na nizsi frekvence." << std::endl;
		options->Print();
	}
	/** Vrati detaily nastaveni algoritmu.
	 * \return popis nastaveni algoritmu
	 */
	std::string GetDetail() {
		return "SA: " + options->GetTitle();
	}
private:
	void CountEnergy(AnnealingSolution & solution);
	void GetNeighbour(AnnealingSolution & base, AnnealingSolution & neighbour);
	bool IsBetter(int newer, int old, double temperature);
	void LowerFrequencies(AnnealingSolution & solution);
	void SetStartingTemperature(AnnealingSolution & best, AnnealingSolution & current);
	AnnealingOptions * options; ///< nastaveni simulovaneho zihani
	Cooler * cooler; ///< chladici strategie
	ConstraintMask * constraints; ///< maska poruseni
};

#endif
